package com.yifushidai.config;


import com.yifushidai.entity.ApiUniqueMac;
import com.yifushidai.mapper.ApiUniqueMacMapper;
import com.yifushidai.utils.CustomPackUtils;
import com.yifushidai.utils.HexUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.service.IoHandler;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.*;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.filter.executor.ExecutorFilter;
import org.apache.mina.filter.keepalive.KeepAliveFilter;
import org.apache.mina.filter.keepalive.KeepAliveMessageFactory;
import org.apache.mina.filter.logging.LogLevel;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.concurrent.Executors;

@Configuration
public class MinaConfig {
    private Logger logger = LoggerFactory.getLogger(MinaConfig.class);
    //客户端心跳请求信息的标志
    private static final String KA_REQUEST = "0001";
    private static final String KA_REQUEST_HEX = "303030310D0A";
    //返回给客户端的心跳反馈信息的标志
    private static final String KA_RESPONSE = "0002";

    @Value("${shisu.mina.port}")
    private int MINA_PORT;

    @Value("${shisu.mina.idle}")
    private int MINA_IDLE;

    @Autowired
    private ApiUniqueMacMapper apiUniqueMacMapper;

    @Bean
    public LoggingFilter loggingFilter() {
        LoggingFilter loggingFilter = new LoggingFilter();
//        loggingFilter.setSessionClosedLogLevel(LogLevel.NONE);
//        loggingFilter.setSessionCreatedLogLevel(LogLevel.NONE);
//        loggingFilter.setSessionOpenedLogLevel(LogLevel.INFO);
        loggingFilter.setSessionIdleLogLevel(LogLevel.NONE);
        return loggingFilter;
    }

    @Bean
    public IoHandler ioHandler() {
        return new ShisuIoHandler();
    }

    @Bean
    public InetSocketAddress inetSocketAddress() {
        return new InetSocketAddress(MINA_PORT);
    }

    @Bean
    public IoAcceptor ioAcceptor() throws Exception {
        IoAcceptor acceptor = new NioSocketAcceptor(10);
        //设置过滤器链  添加log
        acceptor.getFilterChain().addLast("logger", loggingFilter());
        acceptor.getFilterChain().addLast("threadPool", new ExecutorFilter(Executors.newCachedThreadPool()));
        acceptor.setHandler(ioHandler());//ioHandler() //指定业务逻辑处理器
        acceptor.getSessionConfig().setReadBufferSize(2 * 1024); //设置读缓冲区大小
//        acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 30);  //读写都空闲时间:30秒
//        acceptor.getSessionConfig().setIdleTime(IdleStatus.READER_IDLE, 40);//读(接收通道)空闲时间:40秒
//        acceptor.getSessionConfig().setIdleTime(IdleStatus.WRITER_IDLE, 50);//写(发送通道)空闲时间:50秒
        // 设置心跳协议 部分
        KeepAlive ka = new KeepAlive();
        // 说明：实例化一个 KeepAliveFilter 过滤器，传入 KeepAliveMessageFactory引用，IdleStatus参数为 BOTH_IDLE,及表明如果当前连接的读写通道都空闲的时候在指定的时间间隔getRequestInterval后发送出发Idle事件。
        KeepAliveFilter kaf = new KeepAliveFilter(ka, IdleStatus.BOTH_IDLE);//,IdleStatus.BOTH_IDLE //, KeepAliveRequestTimeoutHandler.CLOSE
        // 说明：尤其 注意该句话，使用了 KeepAliveFilter之后，IoHandlerAdapter中的 sessionIdle方法默认是不会再被调用的！ 所以必须加入这句话 sessionIdle才会被调用
        kaf.setForwardEvent(true);//设置是否forward到下一个filter 不设置默认false //idle事件回发  当session进入idle状态的时候 依然调用handler中的idled方法
        // 说明：设置心跳包请求时间间隔，其实对于被动型的心跳机制来说，设置心跳包请求间隔貌似是没有用的，因为它是不会发送心跳包的，但是它会触发 sessionIdle事件，
        // 我们利用该方法，可以来判断客户端是否在该时间间隔内没有发心跳包，一旦 sessionIdle方法被调用，则认为 客户端丢失连接并将其踢出 。因此其中参数 keepAliveRequestInterval 其实就是服务器对于客户端的IDLE监控时间。
        kaf.setRequestInterval(MINA_IDLE);//心跳频率，不设置则默认60s //设置进入Idle状态的时间，单位为s //本服务器为被动型心跳  即需要每6秒内接受一个心跳请求  否则该连接进入空闲状态 并且发出idled方法回调
        // 设置 心跳频率 < 闲置时间 < 断线重连时间（重连时间-最近心跳时间）
        //客户端会定时发送心跳请求（注意定时时间必须小于，服务器端的IDLE监控时间）,同时需要监听心跳反馈
//        kaf.setRequestTimeoutHandler(new KeepAliveTimeOutServerHandler());
//        kaf.setRequestTimeout(10);//心跳超时时间，不设置则默认30s //超时时间 如果当前发出一个心跳请求后需要反馈  若反馈超过此时间 默认则关闭连接
        // 说明： 该过滤器加入到整个通信的过滤链中。
        acceptor.getFilterChain().addLast("heart", kaf);
        //设置自定义编解码
        acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"))));//编码过滤器，使得服务器可以处理string类型的消息
        // 启动服务
        acceptor.bind(inetSocketAddress());
        logger.warn("=== MINA_PORT：{}", MINA_PORT);
        return acceptor;
    }

//    @Value("${apartment.mina.port}")
//    private int apartment_mina_port;
//
//    @Bean
//    public LoggingFilter apartmentLoggingFilter() {
//        LoggingFilter loggingFilter = new LoggingFilter();
////        loggingFilter.setSessionClosedLogLevel(LogLevel.NONE);
////        loggingFilter.setSessionCreatedLogLevel(LogLevel.NONE);
////        loggingFilter.setSessionOpenedLogLevel(LogLevel.INFO);
//        loggingFilter.setSessionIdleLogLevel(LogLevel.NONE);
//        return loggingFilter;
//    }
//
//    @Bean
//    public IoHandler apartmentIoHandler() {
//        return new ApartmentIoHandler();
//    }
//
//    @Bean
//    public InetSocketAddress apartmentInetSocketAddress() {
//        return new InetSocketAddress(apartment_mina_port);
//    }
//
//    @Bean
//    public IoAcceptor apartmentIoAcceptor() throws Exception {
//        IoAcceptor acceptor = new NioSocketAcceptor(10);
//        //设置过滤器链  添加log
//        acceptor.getFilterChain().addLast("logger", apartmentLoggingFilter());
//        acceptor.getFilterChain().addLast("threadPool", new ExecutorFilter(Executors.newCachedThreadPool()));
//        acceptor.setHandler(apartmentIoHandler());//ioHandler() //指定业务逻辑处理器
//        acceptor.getSessionConfig().setReadBufferSize(2 * 1024); //设置读缓冲区大小
////        acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 30);  //读写都空闲时间:30秒
////        acceptor.getSessionConfig().setIdleTime(IdleStatus.READER_IDLE, 40);//读(接收通道)空闲时间:40秒
////        acceptor.getSessionConfig().setIdleTime(IdleStatus.WRITER_IDLE, 50);//写(发送通道)空闲时间:50秒
//        // 设置心跳协议 部分
//        ApartmentKeepAlive ka = new ApartmentKeepAlive(lockMapper);
//        // 说明：实例化一个 KeepAliveFilter 过滤器，传入 KeepAliveMessageFactory引用，IdleStatus参数为 BOTH_IDLE,及表明如果当前连接的读写通道都空闲的时候在指定的时间间隔getRequestInterval后发送出发Idle事件。
//        KeepAliveFilter kaf = new KeepAliveFilter(ka, IdleStatus.BOTH_IDLE);//,IdleStatus.BOTH_IDLE //, KeepAliveRequestTimeoutHandler.CLOSE
//        // 说明：尤其 注意该句话，使用了 KeepAliveFilter之后，IoHandlerAdapter中的 sessionIdle方法默认是不会再被调用的！ 所以必须加入这句话 sessionIdle才会被调用
//        kaf.setForwardEvent(true);//设置是否forward到下一个filter 不设置默认false //idle事件回发  当session进入idle状态的时候 依然调用handler中的idled方法
//        // 说明：设置心跳包请求时间间隔，其实对于被动型的心跳机制来说，设置心跳包请求间隔貌似是没有用的，因为它是不会发送心跳包的，但是它会触发 sessionIdle事件，
//        // 我们利用该方法，可以来判断客户端是否在该时间间隔内没有发心跳包，一旦 sessionIdle方法被调用，则认为 客户端丢失连接并将其踢出 。因此其中参数 keepAliveRequestInterval 其实就是服务器对于客户端的IDLE监控时间。
//        kaf.setRequestInterval(20);//心跳频率，不设置则默认60s //设置进入Idle状态的时间，单位为s //本服务器为被动型心跳  即需要每6秒内接受一个心跳请求  否则该连接进入空闲状态 并且发出idled方法回调
//        // 设置 心跳频率 < 闲置时间 < 断线重连时间（重连时间-最近心跳时间）
//        //客户端会定时发送心跳请求（注意定时时间必须小于，服务器端的IDLE监控时间）,同时需要监听心跳反馈
////        kaf.setRequestTimeoutHandler(new KeepAliveTimeOutServerHandler());
////        kaf.setRequestTimeout(10);//心跳超时时间，不设置则默认30s //超时时间 如果当前发出一个心跳请求后需要反馈  若反馈超过此时间 默认则关闭连接
//        // 说明： 该过滤器加入到整个通信的过滤链中。
//        acceptor.getFilterChain().addLast("heart", kaf);
//        //设置自定义编解码 接收与发送16进制数据包：不设置过滤器
//        acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new MyCodecFcatory()));//编码过滤器，使得服务器可以处理16进制消息
//        // 启动服务
//        acceptor.bind(apartmentInetSocketAddress());
//        logger.warn("=== apartment_mina_port：{}", apartment_mina_port);
//        return acceptor;
//    }

    /**
     * 心跳信息工厂类,采用的是被动心跳机制
     */
    public class KeepAlive implements KeepAliveMessageFactory {
        @Override//判断是否心跳请求包  是的话返回true
        // Returns true if and only if the specified message is a keep-alive request message.
        public boolean isRequest(IoSession ioSession, Object o) {
            String uniqueMac = (String) ioSession.getAttribute("UniqueMac");
            String powerMac = (String) ioSession.getAttribute("PowerMac");
            String remoteAddressAndSocket = (String) ioSession.getAttribute("remoteAddressAndSocket");
            IoBuffer ioBuffer = (IoBuffer) o;
            String message = ioBuffer.getHexDump().replaceAll("\\s*", "");
//        logger.debug("<<< 硬件端：{}({}_{}) 心跳：{}", remoteAddressAndSocket, uniqueMac, powerMac, message);

            boolean uniqueFlag = StringUtils.isNotBlank(uniqueMac) && uniqueMac.matches("^\\w{24}$");
            if (!uniqueFlag) return false;
            boolean powerFlag = StringUtils.isNotBlank(powerMac) && powerMac.matches("^\\w{24}$");
            boolean keepAliveFlag = message.equals(KA_REQUEST_HEX);
            return keepAliveFlag;
        }

        @Override//由于被动型心跳机制，没有请求当然也就不关注反馈 因此直接返回false
        // Returns true if and only if the specified message is a keep-alive response message;
        public boolean isResponse(IoSession session, Object message) {
            return false;
        }

        @Override//被动型心跳机制无请求  因此直接返回null
        // Returns a (new) keep-alive request message.
        public Object getRequest(IoSession session) {
            return null;
        }

        @Override //根据心跳请求request 反回一个心跳反馈消息 non-nul
        // Returns a (new) response message for the specified keep-alive request.
        public Object getResponse(IoSession ioSession, Object o) {
            String uniqueMac = (String) ioSession.getAttribute("UniqueMac");
            String powerMac = (String) ioSession.getAttribute("PowerMac");
            String remoteAddressAndSocket = (String) ioSession.getAttribute("remoteAddressAndSocket");
            IoBuffer ioBuffer = (IoBuffer) o;
            String message = ioBuffer.getHexDump().replaceAll("\\s*", "");
            logger.debug("<<< 硬件端：{}({}_{}) 心跳：{}", remoteAddressAndSocket, uniqueMac, powerMac, message);

            boolean uniqueFlag = StringUtils.isNotBlank(uniqueMac) && uniqueMac.matches("^\\w{24}$");
            if (!uniqueFlag) return false;
            boolean powerFlag = StringUtils.isNotBlank(powerMac) && powerMac.matches("^\\w{24}$");
            boolean keepAliveFlag = message.equals(KA_REQUEST_HEX);
            if (!keepAliveFlag) return false;

            //心跳请求 强制更新在线状态
            ApiUniqueMac one = apiUniqueMacMapper.selectOne(ApiUniqueMac.builder().mac(uniqueMac).build());
            if (null == one) return null;
            if (!powerFlag && one.getLs() == 0) {//uniqueMac通道
                one.setLs(CustomPackUtils.ON_LINE);
                apiUniqueMacMapper.updateByPrimaryKeySelective(one);
            }
            if (powerFlag && one.getPowerStatus() == 0) {//powerMac通道
                one.setPowerStatus(CustomPackUtils.ON_LINE);
                apiUniqueMacMapper.updateByPrimaryKeySelective(one);
            }

            //心跳响应
            ioSession.write(KA_RESPONSE);
            logger.debug(">>> 硬件端：{}({}_{}) 心跳：{}", remoteAddressAndSocket, uniqueMac, powerMac, KA_RESPONSE);
            return null;
        }
    }

    /**
     * 心跳信息工厂类,采用的是被动心跳机制
     */
    public class ApartmentKeepAlive implements KeepAliveMessageFactory {
        @Override//判断是否心跳请求包  是的话返回true
        // Returns true if and only if the specified message is a keep-alive request message.
        public boolean isRequest(IoSession ioSession, Object o) {
            String uniqueMac = (String) ioSession.getAttribute("UniqueMac");
            String powerMac = (String) ioSession.getAttribute("PowerMac");
            String remoteAddressAndSocket = (String) ioSession.getAttribute("remoteAddressAndSocket");
            IoBuffer ioBuffer = (IoBuffer) o;
            String message = ioBuffer.getHexDump().replaceAll("\\s*", "");
//        logger.debug("<<< 硬件端：{}({}_{}) 心跳：{}", remoteAddressAndSocket, uniqueMac, powerMac, message);

            boolean uniqueFlag = StringUtils.isNotBlank(uniqueMac) && uniqueMac.matches("^\\w{24}$");
            if (!uniqueFlag) return false;
            boolean powerFlag = StringUtils.isNotBlank(powerMac) && powerMac.matches("^\\w{24}$");
            if (!message.startsWith(CustomPackUtils.HEAD_HEX)) return false;
            byte[] mainPackage = HexUtils.hexStringToBytes(message);
            if (mainPackage == null) return false;
            byte[] payloadBytes = CustomPackUtils.unpack(mainPackage);
            if (payloadBytes == null) return false;
            byte sourceByte = mainPackage[CustomPackUtils.HEAD_OFFSET + CustomPackUtils.LEN_OFFSET];
            byte destByte = mainPackage[CustomPackUtils.HEAD_OFFSET + CustomPackUtils.LEN_OFFSET + CustomPackUtils.SOURCE_OFFSET];
            if (destByte != CustomPackUtils.SERVER_DEVICE_ID) return false;
            byte msgByte = mainPackage[CustomPackUtils.HEAD_OFFSET + CustomPackUtils.LEN_OFFSET + CustomPackUtils.SOURCE_OFFSET + CustomPackUtils.DEST_OFFSET];
            boolean keepAliveFlag = (msgByte == CustomPackUtils.KA_REQUEST_MSG_ID) && CustomPackUtils.KA_REQUEST_HEX_CMD.equals(HexUtils.bytesToHexString(payloadBytes));
            return keepAliveFlag;
        }

        @Override//由于被动型心跳机制，没有请求当然也就不关注反馈 因此直接返回false
        // Returns true if and only if the specified message is a keep-alive response message;
        public boolean isResponse(IoSession session, Object message) {
            return false;
        }

        @Override//被动型心跳机制无请求  因此直接返回null
        // Returns a (new) keep-alive request message.
        public Object getRequest(IoSession session) {
            return null;
        }

        @Override //根据心跳请求request 反回一个心跳反馈消息 non-nul
        // Returns a (new) response message for the specified keep-alive request.
        public Object getResponse(IoSession ioSession, Object o) {
            String uniqueMac = (String) ioSession.getAttribute("UniqueMac");
            String powerMac = (String) ioSession.getAttribute("PowerMac");
            String remoteAddressAndSocket = (String) ioSession.getAttribute("remoteAddressAndSocket");
            IoBuffer ioBuffer = (IoBuffer) o;
            String message = ioBuffer.getHexDump().replaceAll("\\s*", "");
            logger.debug("<<< 硬件端：{}({}_{}) 心跳：{}", remoteAddressAndSocket, uniqueMac, powerMac, message);

            boolean uniqueFlag = StringUtils.isNotBlank(uniqueMac) && uniqueMac.matches("^\\w{24}$");
            if (!uniqueFlag) return false;
            boolean powerFlag = StringUtils.isNotBlank(powerMac) && powerMac.matches("^\\w{24}$");
            if (!message.startsWith(CustomPackUtils.HEAD_HEX)) return false;
            byte[] mainPackage = HexUtils.hexStringToBytes(message);
            if (mainPackage == null) return false;
            byte[] payloadBytes = CustomPackUtils.unpack(mainPackage);
            if (payloadBytes == null) return false;
            byte sourceByte = mainPackage[CustomPackUtils.HEAD_OFFSET + CustomPackUtils.LEN_OFFSET];
            byte destByte = mainPackage[CustomPackUtils.HEAD_OFFSET + CustomPackUtils.LEN_OFFSET + CustomPackUtils.SOURCE_OFFSET];
            if (destByte != CustomPackUtils.SERVER_DEVICE_ID) return false;
            byte msgByte = mainPackage[CustomPackUtils.HEAD_OFFSET + CustomPackUtils.LEN_OFFSET + CustomPackUtils.SOURCE_OFFSET + CustomPackUtils.DEST_OFFSET];
            boolean keepAliveFlag = (msgByte == CustomPackUtils.KA_REQUEST_MSG_ID) && CustomPackUtils.KA_REQUEST_HEX_CMD.equals(HexUtils.bytesToHexString(payloadBytes));
            if (!keepAliveFlag) return false;

            //心跳响应
            byte[] responseBytes = CustomPackUtils.pack(CustomPackUtils.KA_RESPONSE_HEX_CMD, CustomPackUtils.SERVER_DEVICE_ID, sourceByte, CustomPackUtils.KA_RESPONSE_MSG_ID);
            ioSession.write(IoBuffer.wrap(responseBytes));
            logger.debug(">>> 硬件端：{}({}_{}) 心跳：{}", remoteAddressAndSocket, uniqueMac, powerMac, HexUtils.bytesToHexString(responseBytes));

            //心跳请求 强制更新在线状态
            ApiUniqueMac one = apiUniqueMacMapper.selectOne(ApiUniqueMac.builder().mac(uniqueMac).build());
            if (!powerFlag && one.getLs() == 0) {//uniqueMac通道
                one.setLs(CustomPackUtils.ON_LINE);
                apiUniqueMacMapper.updateByPrimaryKeySelective(one);
            }
            if (powerFlag && one.getPowerStatus() == 0) {//powerMac通道
                one.setPowerStatus(CustomPackUtils.ON_LINE);
                apiUniqueMacMapper.updateByPrimaryKeySelective(one);
            }
            return null;
        }
    }

    /**
     * 自定义编解码类
     * 此处使用了mina自带的TextLineEncoder编解码器，此解码器支持使用固定长度或者固定分隔符来区分上下两条消息。如果要使用自定义协议，则需要自己编写解码器
     */
    public class MyCodecFcatory implements ProtocolCodecFactory {
        private ProtocolEncoder encoder = null;
        private ProtocolDecoder decoder = null;

        public MyCodecFcatory() {
            this.encoder = new ByteArrayEncoder();
            this.decoder = new ByteArrayDecoder();
        }

        @Override
        public ProtocolEncoder getEncoder(IoSession session) throws Exception {
            return this.encoder;
        }

        @Override
        public ProtocolDecoder getDecoder(IoSession session) throws Exception {
            return this.decoder;
        }
    }

    /**
     * 编码器将数据直接发出去(不做处理)
     */
    public class ByteArrayEncoder extends ProtocolEncoderAdapter {
        @Override
        public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception {
            out.write(message);
            out.flush();
        }
    }

    /**
     * 自定义解码器(确保能读取到完整的包)
     */
    public class ByteArrayDecoder extends CumulativeProtocolDecoder {

        @Override
        public boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
            String message = in.getHexDump().replaceAll("\\s*", "");
            byte[] mainPackage = HexUtils.hexStringToBytes(message);
//        System.out.println(message);
//        System.out.println(mainPackage.length);

            if (in.remaining() > CustomPackUtils.HEADER_LENGTH) {//前7字节是包头
                //标记当前position的快照标记mark，以便后继的reset操作能恢复position位置
                in.mark();
                byte[] l = new byte[CustomPackUtils.HEADER_LENGTH];
                in.get(l);

                int payloadLen = 0;
                if (message.startsWith(CustomPackUtils.HEAD_HEX)) {
                    byte[] lenBytes = new byte[CustomPackUtils.LEN_OFFSET];
                    System.arraycopy(l, CustomPackUtils.HEAD_OFFSET, lenBytes, 0, CustomPackUtils.LEN_OFFSET);
                    payloadLen = HexUtils.bytes2Int(lenBytes);//有效载荷长度：有效载荷PAYLOAD的长度
                }

                //注意上面的get操作会导致下面的remaining()值发生变化
                if (in.remaining() < payloadLen + CustomPackUtils.XOR_OFFSET) {
                    //如果消息内容不够，则重置恢复position位置到操作前,进入下一轮, 接收新数据，以拼凑成完整数据
                    in.reset();
                    return false;
                } else {//消息内容足够，或协议头不正确，将消息消费掉
                    in.reset();//重置恢复position位置到操作前
                    byte[] packArr = new byte[mainPackage.length];//总长 = 包头+包体
                    in.get(packArr, 0, mainPackage.length);

                    // 读取内容，并且发送
                    IoBuffer buffer = IoBuffer.allocate(mainPackage.length);
                    buffer.put(packArr);
                    buffer.flip();
                    out.write(buffer);
                    buffer.free();

                    if (in.remaining() > 0) {//如果读取一个完整包内容后还粘了包，就让父类再调用一次，进行下一次解析
                        return true;
                    }
                }
            }
            return false;// 断包，或者执行完，让父类进行接收下个包
        }

    }
}